from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from app.models import Contract, Price, User
from app.schemas import ContractCreate, ContractStatus, BalanceLogType, ContractPeriod
from app.services.user_service import UserService
from app.services.price_service import price_service
from app.config import settings
import logging
from typing import Optional

logger = logging.getLogger(__name__)


class ContractService:
    
    # Period to seconds mapping
    PERIOD_SECONDS = {
        ContractPeriod.TEN_MINUTES: 600,
        ContractPeriod.THIRTY_MINUTES: 1800,
        ContractPeriod.ONE_HOUR: 3600,
        ContractPeriod.ONE_DAY: 86400,
    }
    
    @staticmethod
    async def create_contract(db: AsyncSession, contract_data: ContractCreate) -> Contract:
        """Create a new contract"""
        # Check if user has sufficient balance
        if not await UserService.check_sufficient_balance(db, contract_data.user_id, contract_data.bet_amount):
            raise ValueError("Insufficient balance")
        
        # Get current price
        current_price_data = price_service.get_latest_price(contract_data.symbol)
        if not current_price_data:
            raise ValueError(f"No price data available for {contract_data.symbol}")
        
        # Calculate expiry timestamp
        period_seconds = ContractService.PERIOD_SECONDS[contract_data.period]
        expiry_timestamp = current_price_data.timestamp + (period_seconds * 1000)
        
        # Create contract
        contract = Contract(
            user_id=contract_data.user_id,
            symbol=contract_data.symbol,
            period=contract_data.period.value,
            direction=contract_data.direction.value,
            bet_amount=contract_data.bet_amount,
            anchor_price=current_price_data.price,
            anchor_timestamp=current_price_data.timestamp,
            expiry_timestamp=expiry_timestamp,
            status=ContractStatus.ACTIVE.value
        )
        
        db.add(contract)
        
        # Deduct balance
        await UserService.update_balance(
            db, 
            contract_data.user_id, 
            -contract_data.bet_amount, 
            BalanceLogType.BET,
            contract.id
        )
        
        await db.commit()
        await db.refresh(contract)
        
        logger.info(f"Created contract {contract.id} for user {contract_data.user_id}: {contract_data.symbol} {contract_data.direction.value} {contract_data.bet_amount} USDT")
        return contract
    
    @staticmethod
    async def get_contract(db: AsyncSession, contract_id: str) -> Optional[Contract]:
        """Get contract by ID"""
        result = await db.execute(select(Contract).where(Contract.id == contract_id))
        return result.scalar_one_or_none()
    
    @staticmethod
    async def get_user_contracts(db: AsyncSession, user_id: str, limit: int = 100) -> list[Contract]:
        """Get user contracts"""
        result = await db.execute(
            select(Contract)
            .where(Contract.user_id == user_id)
            .order_by(Contract.created_at.desc())
            .limit(limit)
        )
        return result.scalars().all()
    
    @staticmethod
    async def get_active_contracts(db: AsyncSession) -> list[Contract]:
        """Get all active contracts"""
        result = await db.execute(
            select(Contract).where(Contract.status == ContractStatus.ACTIVE.value)
        )
        return result.scalars().all()
    
    @staticmethod
    async def get_expired_contracts(db: AsyncSession, current_timestamp: int) -> list[Contract]:
        """Get expired contracts that need settlement"""
        result = await db.execute(
            select(Contract).where(
                and_(
                    Contract.status == ContractStatus.ACTIVE.value,
                    Contract.expiry_timestamp <= current_timestamp
                )
            )
        )
        return result.scalars().all()
    
    @staticmethod
    async def save_price_data(db: AsyncSession, price_data) -> None:
        """Save price data to database"""
        price = Price(
            symbol=price_data.symbol,
            price=price_data.price,
            timestamp=price_data.timestamp
        )
        
        db.add(price)
        await db.commit()
    
    @staticmethod
    async def get_price_at_timestamp(db: AsyncSession, symbol: str, timestamp: int, tolerance_ms: int = 1000) -> Optional[Price]:
        """Get price at specific timestamp with tolerance"""
        # Try exact timestamp first
        result = await db.execute(
            select(Price)
            .where(
                and_(
                    Price.symbol == symbol,
                    Price.timestamp == timestamp
                )
            )
            .limit(1)
        )
        
        price = result.scalar_one_or_none()
        if price:
            return price
        
        # Try within tolerance range
        result = await db.execute(
            select(Price)
            .where(
                and_(
                    Price.symbol == symbol,
                    Price.timestamp >= timestamp - tolerance_ms,
                    Price.timestamp <= timestamp + tolerance_ms
                )
            )
            .order_by(Price.timestamp.asc())
            .limit(1)
        )
        
        return result.scalar_one_or_none()

    @staticmethod
    async def settle_contract(db: AsyncSession, contract: Contract) -> Contract:
        """Settle a contract"""
        # Get price at expiry timestamp
        final_price_data = await ContractService.get_price_at_timestamp(
            db, contract.symbol, contract.expiry_timestamp
        )

        if not final_price_data:
            # Mark as unknown and refund
            contract.status = ContractStatus.UNKNOWN.value
            await UserService.update_balance(
                db,
                contract.user_id,
                contract.bet_amount,
                BalanceLogType.REFUND,
                contract.id
            )
            logger.warning(f"Contract {contract.id} marked as unknown - no price data found")
        else:
            # Calculate result
            contract.final_price = final_price_data.price

            # Determine if prediction was correct
            price_increased = contract.final_price > contract.anchor_price
            prediction_correct = (
                (contract.direction == "up" and price_increased) or
                (contract.direction == "down" and not price_increased)
            )

            if prediction_correct:
                # Calculate profit
                profit_rate = settings.profit_rate_10m if contract.period == "10m" else settings.profit_rate_other
                profit = contract.bet_amount * profit_rate
                contract.profit = profit

                # Return bet amount + profit
                total_return = contract.bet_amount + profit
                await UserService.update_balance(
                    db,
                    contract.user_id,
                    total_return,
                    BalanceLogType.PROFIT,
                    contract.id
                )

                logger.info(f"Contract {contract.id} won: profit {profit} USDT")
            else:
                # Loss - no return (bet amount already deducted)
                contract.profit = -contract.bet_amount
                await UserService.update_balance(
                    db,
                    contract.user_id,
                    0,
                    BalanceLogType.LOSS,
                    contract.id
                )

                logger.info(f"Contract {contract.id} lost: {contract.bet_amount} USDT")

            contract.status = ContractStatus.SETTLED.value

        await db.commit()
        await db.refresh(contract)

        return contract
